#include <kernel.h>

static crh_t					*crh;
static volatile crh_node_t		*crh_queue;
static volatile crh_node_t		*crh_queue_p;
static proc_event_t				 timer_event;

volatile uint32_t timer_tick;
volatile uint32_t timer_freq;

struct timer_s
{
	 int        status;

	 union
	 {
		  struct
		  {
			   int        to_queue;
			   timer_cb_f cb;
			   void      *data;
			   
			   crh_node_t crh_node;
		  };

		  struct timer_s *free_next;
	 };
};

#define TIMER_CLOSED  0
#define TIMER_OPEN    1
#define TIMER_CLOSING 2

#define CRH_NODE_TO_TIMER(node) ((struct timer_s *)						\
								 ((char *)(node) - (int)&(((struct timer_s *)0)->crh_node)))

static void
timer_queue_process(void *_)
{
	 intr_irq_save();
	 
	 crh_queue_p = crh_queue;
	 crh_queue = NULL;
	 if (crh_queue_p == NULL)
	 {
		  intr_irq_restore();
	 }
	 else
	 {
		  while (1)
		  {
			   volatile crh_node_t *cur = crh_queue_p;
			   if (cur == NULL)
			   {
					break;
			   }
			   else if (cur->next == cur)
			   {
					crh_queue_p = NULL;
			   }
			   else crh_queue_p = cur->next;
			   struct timer_s *t = CRH_NODE_TO_TIMER(cur);
			   timer_cb_f cb = t->cb;
			   void *data    = t->data;
			   t->status = TIMER_CLOSED;			   
			   cur->next->prev  = cur->prev;
			   cur->prev->next  = cur->next;

			   intr_irq_restore();

			   cb(data);

			   intr_irq_save();
		  }
		  intr_irq_restore();
	 }
}

static int
timer_queue_append(crh_node_t *node)
{
	 if (node != NULL)
	 {
		  crh_node_t *cur = node;
		  while (1)
		  {
			   struct timer_s *t = CRH_NODE_TO_TIMER(cur);
			   if (t->to_queue == 0)
			   {
					t->status = TIMER_CLOSED;
					if (cur->next == cur)
					{
						 node = NULL;
						 t->cb(t->data);
						 break;
					}

					cur->next->prev = cur->prev;
					cur->prev->next = cur->next;

					if (cur == node)
					{
						 node = cur = cur->next;
						 t->cb(t->data);
					}
					else
					{
						 cur = cur->next;
						 t->cb(t->data);

						 if (cur == node) break;
					}
			   }
			   else
			   {
					t->status = TIMER_CLOSING;
					if ((cur = cur->next) == node) break;
			   }
		  } 

		  if (crh_queue == NULL)
			   crh_queue = node;
		  else if (node != NULL)
		  {
			   crh_node_t *l = node->prev;
			   node->prev = crh_queue->prev;
			   crh_queue->prev = l;

			   node->prev->next = node;
			   l->next = (crh_node_t *)crh_queue;
		  }
	 }
	 
	 if (crh_queue != NULL || crh_queue_p != NULL)
	 {
		  if (timer_event == NULL)
		  {
			   proc_t timer_proc;
			   proc_attach(timer_proc = kthread_create("timer", 8, 0, NULL, NULL, 1));
			   timer_event = proc_event_create(1, timer_proc, timer_queue_process, NULL);
		  }
		  
		  proc_event_activate(timer_event);
	 }
}

void
timer_tick_event(int c)
{
	 while (c > 0)
	 {
		  uint32_t s = crh_max_step(crh);
		  if (s == 0)
		  {
			   /* XXX error if the return is not NULL */
			   crh_set_base(crh, timer_tick += c);
			   break;
		  }
		  
		  if (c < s) s = c;
		  crh_node_t *node = crh_set_base(crh, timer_tick += s);
		  timer_queue_append(node);

		  c -= s;
	 }
}

int
timer_init(void)
{
	 timer_event = NULL;
	 crh_queue = crh_queue_p = NULL;
	 crh = (crh_t *)ekf_alloc(CRH_SIZE);
	 crh_init(crh);
	 crh_set_base(crh, 0);
	 
	 timer_tick = 0;

	 return 0;
}

void
timer_freq_measure(void)
{
	 uint32_t old_hpet_tick = *hpet_tick;
	 uint32_t old_tick = timer_tick_get();

	 /* If you use PIC, the HPET and PIT use the same irq 0. And you failed >.< */
#if 1
	 while (((double)(*hpet_tick - old_hpet_tick) / hpet_tick_freq) < 1) ;
#else
	 proc_legacy_wait_pretend();
	 int t = hpet_timer_open_ms((hpet_timer_cb_f)proc_legacy_notify, (void *)proc_self(), 2000);
	 proc_legacy_wait_try();
	 hpet_timer_close(t);
	 hpet_timer_free(t);
#endif

	 uint32_t new_hpet_tick = *hpet_tick;
	 uint32_t new_tick = timer_tick_get();

	 uint32_t freq = (new_tick - old_tick) / ((double)(new_hpet_tick - old_hpet_tick) / hpet_tick_freq) * 2 + 1;
	 timer_freq = freq >> 1;
}

timer_t 
timer_open(timer_cb_f cb, void *data, int to_queue, uint32_t tick)
{
	 struct timer_s *r = (struct timer_s *)ekf_alloc(sizeof(struct timer_s));
	 if (r == NULL)
		  return NULL;

	 r->status       = TIMER_OPEN;
	 r->cb           = cb;
	 r->data         = data;
	 r->to_queue     = to_queue;
	 r->crh_node.key = tick;

	 intr_irq_save();
	 if (crh_insert(crh, &r->crh_node) != 0)
	 {
		  r->status = TIMER_CLOSING;
		  r->crh_node.next = r->crh_node.prev = &r->crh_node;
		  timer_queue_append(&r->crh_node);
	 }
	 intr_irq_restore_schedule();
	 return r;
}

void
timer_close(timer_t timer)
{
	 intr_irq_save();
	 
	 switch (timer->status)
	 {
	 case TIMER_OPEN:
		  crh_remove(crh, &timer->crh_node);
		  break;
	 case TIMER_CLOSING:
		  if (crh_queue_p == &timer->crh_node)
		  {
			   if (timer->crh_node.next != &timer->crh_node)
			   {
					crh_queue_p = timer->crh_node.next;
			   }
			   else crh_queue_p = NULL;
		  }
		  timer->crh_node.next->prev = timer->crh_node.prev;
		  timer->crh_node.prev->next = timer->crh_node.next;
		  break;
	 }
	 intr_irq_restore();

	 ekf_free(timer);
}

int
timer_is_closed(timer_t timer)
{
	 return timer->status == TIMER_CLOSED;
}
